package intopo.evogame.partnerselection.population;

import intopo.evogame.partnerselection.config.ConstructStrategyException;
import intopo.evogame.partnerselection.config.ParsingException;
import intopo.evogame.partnerselection.config.TagException;
import intopo.evogame.partnerselection.player.AbstractPlayer;
import intopo.evogame.partnerselection.player.PlayerFactory;
import intopo.evogame.partnerselection.chromosome.Chromosome;
import intopo.evogame.partnerselection.chromosome.ChromosomeFactory;
import intopo.evogame.partnerselection.config.AbstractTextConfigFile;
import intopo.game.Strategy;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.Scanner;

/**
 * A population where players have a physical location.
 *
 * <p> This class is generified.  It can handle any type of strategies, chromosomes and players.  In order to construct chromosomes or players, the constructor receives two factories.  To construct strategies we use an associative memory.  A game may have different classes that implement different strategy spaces.  This allows the programmer to develop new games.
 *
 *
 * <h3>Configuration File</h3>
 *
 * @author Pedro Mariano
 * @param <S>
 * @param <C>
 * @param <P>
 */
public final class GeographicPopulation<
		  S extends Strategy<S>,
		  C extends Chromosome<S>,
		  P extends AbstractPlayer<S, C, P>>
{
	/**
	 * Nodes in the population.
	 */
	final private Node[] nodes;
	/**
	 * Landscape width;
	 */
	final private int width;
	/**
	 * Landscape height;
	 */
	final private int height;
	/**
	 * Landscape geometry;
	 */
	final private Geometry geometry;
	/**
	 * Players in this population.
	 */
	final private PlayerCollection<S, C, P> players;
	/**
	 * Creates a geographic population from the data available in the given scanner.
	 * <p> The creation of a population has the following steps:
	 *
	 * <ol>
	 *
	 * <li> Construction of nodes.  For each node players are constructed. The set of candidate partners is not initialised and the probability and combination vectors is not constructed. </li>
	 *
	 * <li> Creation of links between nodes. </li>
	 *
	 * <li> Initialisation of players' set of candidate partners.  For every player in a node, we add to his set of candidate partners the players in that node and in neighbouring nodes.  This is performed by method {@code Node#initPlayersNeighbours()} </li>
	 *
	 * <li> Construction and initialisation of players' probability and combination vectors.  For players that do not have suficient partners, this data is not constructed.  Instead it is postponed until new players are added to their nodes. </li>
	 * </ol>
	 * 
	 * @param scanner The scanner where the data is read.
	 * @param playerFactory  Factory used to construst players.
	 * @param chromosomeFactory  Factory used to construct player's chromosome.
	 * @param map  The associative memory that contains strategy constructors.
	 * @throws java.lang.InstantiationException
	 * @throws java.lang.IllegalAccessException
	 * @throws java.lang.IllegalArgumentException
	 * @throws java.lang.reflect.InvocationTargetException
	 * @throws java.io.IOException  If an IO error occurred while writing the data of a constructed player.
	 * @throws intopo.evogame.partnerselection.TagException
	 */
	public GeographicPopulation (Scanner scanner, PlayerFactory<S, C, P> playerFactory, ChromosomeFactory<S, C> chromosomeFactory, Map<String, Constructor<? extends S>> map)
		throws
			IOException,
			TagException,
			ConstructStrategyException,
			ParsingException
	{
		this.width = AbstractTextConfigFile.readInt (scanner, "environment width");
		this.height = AbstractTextConfigFile.readInt (scanner, "environment height");
		try {
			this.geometry = Geometry.values () [AbstractTextConfigFile.readInt (scanner, "environment geometry")];
		}
		catch (IndexOutOfBoundsException e) {
			throw new ParsingException ("Invalid geometry ordinal");
		}
		boolean boundary = AbstractTextConfigFile.readInt (scanner, "environment boundary flag") == 1;
		this.players = new PlayerCollection<S, C, P> ();
		this.nodes = new Node [width * height];
		int i = this.nodes.length;
		// read nodes
		while (i > 0) {
			i--;
			Node<S, C, P> node;
			node = new Node<S, C, P> (scanner, playerFactory, chromosomeFactory, map);
			this.nodes [i] = node;
			for (P p : node.players) {
				this.players.add (p);
			}
		}
		// initialise links
		int x, y;
		for (x = this.width - 1; x >= 0; x--) {
			for (y = this.height - 1; y >= 0; y--) {
				i = x + y * this.width;
				for (int[] delta : this.geometry.neighbourhoodDelta) {
					int nx = x + delta [0];
					int ny = y + delta [1];
					if (boundary && (nx < 0 || nx >= this.width || ny < 0 || ny >= this.height)) {
						continue ;
					}
					if (nx < 0) {
						nx += this.width;
					}
					else if (nx >= this.width) {
						nx -= this.width;
					}
					if (ny < 0) {
						ny += this.height;
					}
					else if (ny >= this.height) {
						ny -= this.height;
					}
//					System.out.format ("(%2d,%2d) -> (%2d,%2d)\n", x, y, nx, ny);
					this.nodes [nx + ny * this.width].link (this.nodes [i]);
				}
			}
		}
		// initialise players' neighbours
		for (Node n : this.nodes) {
			n.initPlayersNeighbours ();
		}
		System.out.format ("%d players in the population\n", this.players.size ());
		i = 0;
		for (P p : this.players) {
			System.out.format ("\r%d", i++);
			System.out.flush ();
			p.initProbabilityCombinationVectors ();
		}
		System.out.println ();
	}
	public PlayerCollection<S, C, P> getPlayers ()
	{
		return this.players;
	}
	/**
	 * Calculates the next generation of players. Players that have reached the reproduction phase produce offspring.  Check if any player dies and remove him from the node and the population.
	 * Update the set of candidate partners (players neighbours).
	 */
	public boolean nextGeneration (int time)
		throws IOException
	{
		boolean result = true;
		for (Node n : this.nodes) {
			n.calcNurseryCemetry (time);
			result = n.neighbourDied ();
			n.placeOffspring (time);
		}
		for (Node n : this.nodes) {
			n.updatePlayerNeighboursPopulationPlayers (this.players);
		}
		return result;
	}


	public void debug ()
	{
		for (Node n : this.nodes) {
			n.debug ();
		}
	}
	/**
	 * Geometry of a geographic population.  This determines the neighbourhood of a node.
	 */
	public enum Geometry
	{
		/**
		 * Moore geometry where each node is connected to eight other nodes.
		 */
		MOORE ("Moore", new int[][] {
			{-1,  1}, {0,  1}, {1,  1},
			{-1,  0},          {1,  0},
			{-1, -1}, {0, -1}, {1, -1}}),
		/**
		 * Von Neumann geometry where each node is connnected to four other nodes.
		 */
		VON_NEUMANN ("Von Neumann", new int[][] {
			         {0,  1},
			{-1, 0},          {1, 0},
			         {0, -1}}),
		/**
		 * Hexagonal geometry where each node is connected to six other nodes.
		 */
		HEXAGONAL ("Hexagonal", new int[][] {
			          {0,  1}, {1, 1},
			{-1,  0},          {1, 0},
			{-1, -1}, {0, -1}});
		/**
		 * Matrix containing the vectors that when added to a position, returns a neighbouring position.  Field {@code neighbourhoodDelta.length}
		 * indicates the number of neighbours of a node.
		 */
		public int[][] neighbourhoodDelta;
		/**
		 * Description of this enum suitable to be used in GUI.
		 */
		public String description;
		private Geometry (String description, int[][] neighbourhoodDelta)
		{
			this.description = description;
			this.neighbourhoodDelta = neighbourhoodDelta;
		}
		@Override
		public String toString ()
		{
			return this.description;
		}
	}
//	/**
//	 * Represents a node in a networked population of players.
//	 *
//	 * <p>A population of players is organised in a network.  Nodes in the network can contain any number of players subject to some carrying capacity.  Players in a node can interact with players within it but also with players in neighbouring nodes.  The probability to interact with an inside player is equal to the probability of interacting with a player from a neighbouring node.
//	 */
//	final static class Node<
//		  S extends Strategy<S>,
//		  C extends Chromosome<S>,
//		  P extends AbstractPlayer<S, C, P>>
//	{
//		/**
//		 * Node carrying capacity.
//		 */
//		final int carryingCapacity;
//		/**
//		 * Players currently in the node.
//		 */
//		final LinkedList<P> players;
//		/**
//		 * Neighbouring nodes of this one.  Born players can be placed in the node's parent player or in one neighbour node.
//		 */
//		final LinkedList<Node> neighbourNodes;
//		transient final private LinkedList<P> nursery = new LinkedList<P> ();
//		transient final private LinkedList<P> cemetery = new LinkedList<P> ();
//		Node (int carryingCapacity)
//		{
//			this.carryingCapacity = carryingCapacity;
//			this.players = new LinkedList<P> ();
//			this.neighbourNodes = new LinkedList<Node> ();
//		}
//		/**
//		 * Calculates the next generation of players. Players that have reached the reproduction phase are replaced by their offspring.
//		 */
//		private void calcNurseryCemetry ()
//			throws IOException
//		{
//			Iterator<P> iterator;
//			// check who can reproduce and who dies
//			iterator = this.players.iterator ();
//			int size = this.players.size ();
//			while (iterator.hasNext ()) {
//				P p = iterator.next ();
//	//			System.out.print (p.energy);
//	//			System.out.print (' ');
//				P child = p.reproduction ();
//				if (child != null) {
//					this.nursery.add (child);
//				}
//				//FIX carrying capacity
//				if (p.dies ()) {
//					this.cemetery.add (p);
//					iterator.remove ();
//					size--;
//				}
//			}
//	//		System.out.println ();
//	//		System.out.println ("nursery has " + this.nursery.size () + " cemetery has " + this.cemetery.size ());
//		}
//		boolean neighbourDied ()
//		{
//			// update neighbours of remaining players
//			for (P pr : this.players) {
//				for (P pd : this.cemetery) {
//					pr.neighbourDied (pd);
//				}
//			}
//			for (Node<S, C, P> n : this.neighbourNodes) {
//				for (P pr : n.players) {
//					for (P pd : this.cemetery) {
//						pr.neighbourDied (pd);
//					}
//				}
//			}
//			return (this.players.size () + this.nursery.size () >= Cache.numberPlayers);
//		}
//		void addPlayer (P player)
//		{
//			for (P p : this.players) {
//				p.addNeighbour (player);
//				player.addNeighbour (p);
//			}
//			for (Node<S, C, P> n : this.neighbourNodes) {
//				for (P p : n.players) {
//					p.addNeighbour (player);
//					player.addNeighbour (p);
//				}
//			}
//			this.players.add (player);
//		}
//		void placeOffspring ()
//		{
//			for (P pb : this.nursery) {
//				this.addPlayer (pb);
//			}
//		}
//		void updatePlayerNeighbours ()
//		{
//			// initialise probability, combination and weight vectors
//			for (P pb : this.nursery) {
//				pb.initProbabilityCombinationVectors ();
//			}
//			for (P pr : this.players) {
//				pr.checkInvalidCombinations ();
//			}
//			this.nursery.clear ();
//			this.cemetery.clear ();
//		}
//	}
	/**
	 * Population parameters
	 */
	static class Parameters
	{
		static int numberNodes;
	}
}

// Local Variables: 
// mode: flyspell-prog
// ispell-local-dictionary: "british"
// End:
